import Cesium, { Cartesian3, PixelDatatype } from '../Ces/Cesium';

// 定义WeQueryCompute的选项接口
interface WeQueryComputeOptions {
    display?: any;
}

class WeQueryCompute {
    private _strCoord: Cartesian3;
    _textureUniform: any;
    public pixelDatatype: PixelDatatype;
    private _textures//: { [key: string]: Cesium.Texture };
    private _shaders//: { [key: string]: Cesium.ShaderProgram };
    private _readStates: { [key: string]: any };
    private _queryQueue: { strCoord: Cartesian3; resovle?: (result: { value: any }) => void }[];
    public display: any;
    private _readyPromise
    public numX: number;
    public numY: number;
    public query3D: boolean;
    _batchTable
    constructor(options: WeQueryComputeOptions = {}) {
        this._strCoord = new Cesium.Cartesian3(0.0, 0.0, 0.0);
        this._textureUniform = undefined;
        this.pixelDatatype = Cesium.PixelDatatype.UNSIGNED_BYTE;
        this._textures = {};
        this._shaders = {};
        this._readStates = {};
        this._queryQueue = [];
        this.display = options.display;
        this._readyPromise = Cesium.defer();
        this.numX = 1;
        this.numY = 1;
        this.query3D = false;
    }

    // queryQueue的访问器
    get queryQueue(): { strCoord: Cartesian3; resovle?: (result: { value: any }) => void }[] {
        return this._queryQueue;
    }

    // readyPromise的访问器
    get readyPromise(): Promise<any> {
        return this._readyPromise.promise;
    }

    getReadState(pixelDatatype: PixelDatatype, context: any): any {
        const readStates = this._readStates;

        if (!readStates[pixelDatatype]) {
            const outputTexture = this.getTexture(pixelDatatype, context);

            readStates[pixelDatatype] = {
                framebuffer: new Cesium.Framebuffer({
                    context,
                    colorTextures: [outputTexture],
                    destroyAttachments: false,
                }),
                x: 0,
                y: 0,
                width: outputTexture.width,
                height: outputTexture.height,
            };
        }

        return readStates[pixelDatatype];
    }

    getTexture(pixelDatatype: PixelDatatype, context: any) {
        const textures = this._textures;

        if (!textures[pixelDatatype]) {
            textures[pixelDatatype] = new Cesium.Texture({
                context,
                width: this.numX,
                height: this.numY,
                pixelDatatype,
                pixelFormat: Cesium.PixelFormat.RGBA,
                sampler: Cesium.Sampler.NEAREST,
            });
        }

        return textures[pixelDatatype];
    }

    getShader(_texture: any, context: any) {
        const query3D = this.query3D;
        const baseShaderSource = this.display.getFragmentShaderSource(true, true, true);

        const fragmentShaderSource = `
            ${baseShaderSource}
            uniform vec3 str;
#ifdef IS_SAMPLER3D
            vec3 lookup_wind(const vec3 v_particle_pos) {
              bool shouldAnime = Weget_shouldAnime() > 0.0;
              bool shouldFlipY = Weget_shouldFlipY() > 0.0;
              vec3 uv = v_particle_pos;
              if(!shouldFlipY){
                uv.y = 1.0 - uv.y;
              }
              vec3 velocity = WeDecodeVal(u_BaseTexture,uv).rgb;
              if(shouldAnime){
                float animeStep = Weget_animeStep();
                vec3 lastComp = WeDecodeVal(u_LastBaseTexture,uv).rgb;
                velocity = mix(lastComp,velocity,animeStep);
              }
              return velocity;
            }
#else
            vec3 lookup_wind(const vec3 v_particle_pos) {
              bool shouldAnime = Weget_shouldAnime() > 0.0;
              vec3 uv = v_particle_pos;
              vec3 velocity = WeDecodeVal(u_BaseTexture,uv.xy).rgb;
              if(shouldAnime){
                float animeStep = Weget_animeStep();
                vec3 lastComp = WeDecodeVal(u_LastBaseTexture,uv).rgb;
                velocity = mix(lastComp,velocity,animeStep);
              }
              return velocity;
            }
#endif

            vec3 lookup_wind(const vec2 v_particle_pos) {
              return lookup_wind(vec3(v_particle_pos,0.0));
            }
            void main()
            {
              vec3 uvt = str;
              bool isFlipY = Weget_shouldFlipY() > 0.0;
              if(isFlipY){
                uvt.y = 1.0 - uvt.y;
              }
              float value = ${query3D? 'WeDecodeValue(uvt)' : 'WeDecodeValue(uvt.xy)'};
              vec3 wind = lookup_wind(uvt);
              out_FragColor = vec4(value,wind);
            }`;

        return Cesium.ShaderProgram.fromCache({
            context,
            vertexShaderSource: Cesium._shadersViewportQuadVS,
            fragmentShaderSource,
            attributeLocations: {
                position: 0,
            },
        });
    }

    createCompute(context: any, strCoord: Cartesian3, resolve?: (result: { value: any }) => void) {
        const that = this;

        const shaderProgram = this.getShader(that._textureUniform.baseTexture, context);

        const outputTexture = this.getTexture(Cesium.PixelDatatype.FLOAT, context);

        this.pixelDatatype = that._textureUniform.baseTexture.pixelDatatype;
        this.pixelDatatype = Cesium.PixelDatatype.FLOAT;

        const uniformMap = {
            str: () => strCoord,
        };

        this._textureUniform.setupUniformMap(uniformMap);
        this.display.setupUniformMap(uniformMap);

        const vertexArray = context.getViewportQuadVertexArray();

        const computeCommand = new Cesium.ComputeCommand({
            vertexArray,
            shaderProgram,
            outputTexture,
            uniformMap,
            persists: true,
            preExecute() { },
            postExecute() {
                const value = that.getValue(Cesium.PixelDatatype.FLOAT, context);

                if (resolve) {
                    resolve({ value });
                }
            },
        });

        return computeCommand;
    }

    query(strCoord: Cartesian3): Promise<any> {
        const that = this;

        return new Promise((resovle) => {
            that._queryQueue.push({ strCoord, resovle });
        });
    }

    onQuery(value: any): void { }

    getValue(pixelDatatype: PixelDatatype, context: any): { realValue: any; pixels: any[] } {
        const readState = this.getReadState(pixelDatatype, context);
        const pixels = context.readPixels(readState);

        const realValue = pixels[0];

        this.onQuery(realValue);

        return { realValue, pixels };
    }

    pushQuery(strCoord: Cartesian3): void {
        this._queryQueue.push({ strCoord });
    }

    isDestroyed(): boolean {
        return false;
    }

    destroy(): any {
        const that = this;

        Object.keys(this._shaders).forEach((k) => {
            that._shaders[k].destroy();
        });
        Object.keys(this._textures).forEach((k) => {
            that._textures[k].destroy();
        });

        this._shaders = {};
        this._textures = {};
        this._batchTable = this._batchTable && this._batchTable.destroy();

        return Cesium.destroyObject(this);
    }

    update(frameState): void {
        if (!this._batchTable) {
            const attributes = [{
                functionName: 'getQueryST',
                componentDatatype: Cesium.ComponentDatatype.FLOAT,
                componentsPerAttribute: 3,
            }];

            const batchTable = new Cesium.BatchTable(frameState.context, attributes, 1024);

            this._batchTable = batchTable;
        }
        if (this._queryQueue.length > 0) {
            const commands = [];

            for (let i = 0; i < this.queryQueue.length; i++) {
                const { strCoord, resovle } = this.queryQueue[i];
                const command = this.createCompute(frameState.context, strCoord, resovle);

                commands.push(command);
            }

            this._batchTable.update(frameState);

            commands.forEach((c) => {
                frameState.commandList.push(c);
            });

            this._queryQueue.length = 0;
        }
    }
}

export default WeQueryCompute;
